package com.unitd.frame.spring;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;

import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @desc 实例工厂类;通过它可以获得其管理的类的实例
 * InstanceFactory向客户代码隐藏了IoC工厂的具体实现.在后台,它通过InstanceProvider策略接口,允许选择不同的IoC工厂,例如Spring,Google Guice和TapestryIoC等等.
 * IoC工厂应该在应用程序启动时装配好,也就是把初始化好的InstanceProvider实现类提供给InstanceFactory.
 * 对于web应用来说,最佳的初始化方式是创建一个Servlet过滤器或监听器,并部署到web.xml里面;
 * 对普通java应用程序来说,最佳的初始化位置是在main()函数里面;
 * 对于单元测试,最佳的初始 化位置是setUp()方法内部.
 * @filename SpringInstanceFactory.java
 * @copyright www.unitd.com
 * @author Hudan
 * @version 1.0
 * @date 2016/10/14
 */
public class SpringInstanceFactory {

	private static final Logger logger = LoggerFactory.getLogger(SpringInstanceFactory.class.getName());

	private static SpringInstanceProvider springInstanceProvider;
	private static Long timeStarting = System.currentTimeMillis();
	private static AtomicBoolean initialized = new AtomicBoolean(false);

	/**
	 * @desc 设置实例提供者.
	 * @param provider 一个实例提供者的实例.
	 */
	public static void setSpringInstanceProvider(SpringInstanceProvider provider) {
		if (springInstanceProvider != null) {
			return;
		}

		springInstanceProvider = provider;
		initialized.set(true);
	}

	/**
	 * @desc 获取实例提供者.
	 * @return 实体提供者的一个实现类.
	 */
	public static SpringInstanceProvider getSpringInstanceProvider() {
		return springInstanceProvider;
	}

	/**
	 * @desc 获取指定类型的对象实例.如果IoC容器没配置好或者IoC容器中找不到该类型的实例则抛出异常.
	 * @param <T>       对象的类型
	 * @param beanClass 对象的类
	 * @return 类型为T的对象实例
	 */
	public static <T> T getInstance(Class<T> beanClass) {
		return getSpringInstanceProvider().getInstance(beanClass);
	}

	/**
	 * @desc 获取指定类型的对象实例
	 * @param <T>      对象的类型
	 * @param beanName 实现类在容器中配置的名字
	 */
	@SuppressWarnings("unchecked")
	public static <T> T getInstance(String beanName) {
		return (T) getSpringInstanceProvider().getInstance(beanName);
	}

	/**
	 * @desc 获取指定类型的对象实例.如果IoC容器没配置好或者IoC容器中找不到该实例则抛出异常.
	 * @param <T>       对象的类型
	 * @param beanName  实现类在容器中配置的名字
	 * @param beanClass 对象的类
	 * @return 类型为T的对象实例
	 */
	public static <T> T getInstance(Class<T> beanClass, String beanName) {
		return getSpringInstanceProvider().getInstance(beanClass, beanName);
	}

	public static ApplicationContext getContext() {
		return getSpringInstanceProvider().getApplicationContext();
	}

	/**
	 * @desc 这是一个阻塞方法,直到context初始化完成
	 */
	public synchronized static void waitUtilInitialized() {
		if (initialized.get()) {
			return;
		}

		while (true) {
			if (initialized.get()) {
				break;
			}

			try {
				Thread.sleep(1000);
			} catch (Exception e) {
				logger.error("InstanceFactory的阻塞方法,初始化context失败", e);
			}
			long waiting = System.currentTimeMillis() - timeStarting;
			if (waiting > 60 * 1000) {
				logger.error("InstanceFactory的阻塞方法超时,初始化context失败!");
				throw new RuntimeException("InstanceFactory的阻塞方法超时,初始化context失败!");
			}
			logger.info("InstanceFactory的阻塞方法完成初始化context,获取SringContext对象!");
		}
	}

	/**
	 * @desc 判断SpringContext工厂是否已初始化完成
	 * @return boolean
	 */
	public static boolean isInitialized() {
		return initialized.get();
	}
}